using System;
using System.Collections.Generic;
using System.Threading;

namespace Fadd.Globalization
{
    /// <summary>
    /// Contains language translations used to create multilingual applications
    /// </summary>
    /// <remarks>
    /// The LanguageNode provides a base class for different implementations of a hierachial language structure
    /// </remarks>
    public abstract class LanguageNode : ILanguageNode
    {
        /// <summary>
        /// An empty language node (used instead of null).
        /// </summary>
        public static EmptyLanguageNode Empty = new EmptyLanguageNode(0);

        private readonly Dictionary<string, ILanguageNode> _children = new Dictionary<string, ILanguageNode>();
        private int _defaultLCID;
        private string _name = string.Empty;
        private ILanguageNode _parentNode;
        private ILanguageNode _rootNode;

        /// <summary>
        /// Initializes a new instance of the <see cref="LanguageNode"/> class.
        /// </summary>
        /// <param name="defaultLCID">The default LCID.</param>
        protected LanguageNode(int defaultLCID)
        {
            _defaultLCID = defaultLCID;
            _rootNode = this;
        }

        /// <summary>
        /// All child nodes.
        /// </summary>
        protected Dictionary<string, ILanguageNode> Children
        {
            get { return _children; }
        }

        #region ILanguageNode Members

        /// <summary>
        /// Parent language category
        /// </summary>
        public ILanguageNode ParentNode
        {
            get { return _parentNode; }
            internal set
            {
                _parentNode = value;
                if (_parentNode == null)
                    _rootNode = this;
                else
                    _rootNode = _parentNode.RootNode ?? _parentNode;
            }
        }

        /// <summary>
        /// Mother of all nodes.
        /// </summary>
        public virtual ILanguageNode RootNode
        {
            get
            {
                return _rootNode;
            }
        }

        /// <summary>
        /// Get a localized text string in the current language.
        /// </summary>
        /// <param name="textName">Phrase to find.</param>
        /// <returns>text if found; [textName] if not.</returns>
        /// <example>
        /// <code>
        /// lang["Name"] // => "Name"
        /// lang["Naem"] // => "[Naem]" since it's missing
        /// </code>
        /// </example>
        public abstract string this[string textName] { get; }

        /// <summary>
        /// Get a localized text string
        /// </summary>
        /// <param name="lcid"></param>
        /// <param name="textName">Phrase to find.</param>
        /// <returns>text if found; [textName] if not.</returns>
        /// <example>
        /// <code>
        /// lang["Name"] // => "Name"
        /// lang["Naem"] // => "[Naem]" since it's missing
        /// </code>
        /// </example>
        public abstract string this[string textName, int lcid] { get; }

        /// <summary>
        /// Get a text string in the specified language.
        /// </summary>
        /// <param name="lcid">Language to fetch from.</param>
        /// <param name="textName">name of text to find.</param>
        /// <returns>string if found; otherwise null.</returns>
        public abstract string GetText(int lcid, string textName);

        /// <summary>
        /// Get a text string in the specified language.
        /// </summary>
        /// <param name="lcid">Language to fetch from.</param>
        /// <param name="textName">name of text to find.</param>
        /// <param name="checkPaths">check for paths in <paramref name="textName"/>.</param>
        /// <returns>string if found; otherwise null.</returns>
        public abstract string GetText(int lcid, string textName, bool checkPaths);

        /// <summary>
        /// Number languages
        /// </summary>
        public abstract int Count { get; }

        /// <summary>
        /// Returns the name of the node
        /// </summary>
        public string Name
        {
            get { return _name; }
            internal set { _name = value; }
        }

        /// <summary>
        /// LCID to use if the specified or current LCID is not found.
        /// </summary>
        public int DefaultLCID
        {
            get { return _defaultLCID; }
        }

        /// <summary>
        /// Number of translated texts in the specified language
        /// </summary>
        /// <param name="lcid"></param>
        /// <returns></returns>
        public abstract int GetTextCount(int lcid);

        /// <summary>
        /// Determine if the node contains a text item with the specified name (for the current lcid)
        /// </summary>
        /// <param name="name"></param>
        /// <returns>
        /// True if the node contains a language element with the specified name for the current language
        /// </returns>
        public bool Contains(string name)
        {
			return Contains(name, CurrentCulture.LCID);
        }

        /// <summary>
        /// Determine if the node contains a text item with the specified name for the specified language
        /// </summary>
        /// <param name="name"></param>
        /// <param name="lcid"></param>
        /// <returns>
        /// True if the node contains a language element with the specified name for the specified language
        /// </returns>
        public abstract bool Contains(string name, int lcid);

        /// <summary>
        /// Determine if a category contains a specific language.
        /// </summary>
        /// <param name="lcid"></param>
        /// <returns></returns>
        public abstract bool ContainsLanguage(int lcid);

        /// <summary>
        /// Checks if current language contains a child node with the specified name.
        /// </summary>
        /// <param name="name">Name of child node</param>
        /// <returns>true if found; otherwise false.</returns>
        public bool ContainsChild(string name)
        {
            return Children.ContainsKey(name);
        }

        /// <summary>Empties all saved values in the node and its sub nodes</summary>
        public abstract void ClearHierarchy();

        /// <summary>
        /// Gets a sub category
        /// </summary>
        /// <param name="name">The category name</param>
        /// <returns>
        /// 	<see cref="ILanguageNode"/> if found; otherwise null.
        /// </returns>
        /// <exception cref="ArgumentNullException">If name is null</exception>
        public ILanguageNode GetChild(string name)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            if (name == "/")
                return RootNode;

            if (name == "..")
                return ParentNode;

            ILanguageNode node;

            int pos = name.IndexOf('/');
            if (pos != -1)
            {
                if (name[0] == '/')
                    return RootNode.GetChild(name.Remove(0, 1));
                if (name.Substring(0, 3) == "../")
                    return ParentNode != null ? ParentNode.GetChild(name.Remove(0, 3)) : null;

                string nodeName = name.Substring(0, pos);
                string childPath = name.Substring(pos + 1);
                return !Children.TryGetValue(nodeName, out node) ? null : node.GetChild(childPath);
            }

            return Children.TryGetValue(name, out node) ? node : null;
        }

        #endregion

        /// <summary>
        /// Checks paths that are specified in text names
        /// </summary>
        /// <param name="textName">For instance /user/show/firstname</param>
        /// <param name="lcid">language to check in</param>
        /// <returns>string if language contains a path, otherwise null.</returns>
        protected string CheckPaths(int lcid, string textName)
        {
            // relative path
            if (textName.StartsWith("../"))
                return ParentNode != null ? ParentNode.GetText(lcid, textName.Remove(0, 3), true) : null;

            // absolute path
            if (textName[0] == '/')
            {
                return RootNode != null
                           ? RootNode.GetText(lcid, textName.Remove(0, 1), true)
                           : GetText(lcid, textName.Remove(0, 1), true);
            }

            // relative path
            if (Count > 0)
            {
                int pos = textName.IndexOf('/');
                if (pos != -1)
                {
                    string childName = textName.Substring(0, pos);
                    ILanguageNode child = GetChild(childName);
                    return child != null ? child.GetText(lcid, textName.Substring(pos + 1)) : EmptyValue(textName);
                }
            }

            return null;
        }

        /// <summary>
        /// Add a localized text string.
        /// </summary>
        /// <param name="lcid">locale</param>
        /// <param name="name">Name identifying the string. Used to fetch the string later on.</param>
        /// <param name="text">Localized string</param>
        public abstract void Add(string name, int lcid, string text);

        /// <summary>
        /// Sets a localized text string. If the a string with the specified name exists it will be overwritten.
        /// </summary>
        /// <param name="lcid">locale</param>
        /// <param name="name">Name identifying the string. Used to fetch the string later on.</param>
        /// <param name="text">Localized string</param>
        public abstract void Set(string name, int lcid, string text);

        /// <summary>
        /// Adds a sub category
        /// </summary>
        /// <param name="name">Name of the sub category</param>
        /// <exception cref="ArgumentException">If a category with the specified name already exists</exception>
        /// <exception cref="ArgumentNullException">If name is null</exception>
        public abstract LanguageNode AddChild(string name);


        /// <summary>
        /// Value that should be returned if the text is not found.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string EmptyValue(string name)
        {
            return string.Format("[{0}]", name);
        }

        /// <summary>
        /// Set default locale
        /// </summary>
        /// <param name="lcid">Locale to set.</param>
        internal void SetDefaultLCID(int lcid)
        {
            _defaultLCID = lcid;
        }
    }
}